#ifndef __AVOS_BUFFER_H_
#define __AVOS_BUFFER_H_

#include <string.h>

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

/**
 * This is an implementation of circular (ring) buffer with byte-sized
 * elements. It can be used when you get incoming data through interrupts.
 */

/**
 * Buffer structure. Contains size, the amount of actually written data,
 * reading and writing pointers and data buffer itself.
 */
typedef struct {
	size_t size;
	size_t filled;
	char *write_ptr;
	char *read_ptr;
	char data[0];
} buf_t;

/**
 * Create new buffer. On success returns the buffer created, on failure returns
 * NULL pointer.
 */
static inline buf_t *buf_init(void *pool, size_t size)
{
	buf_t *b;
	/* Check if pool is available to store at least buffer header */
	if (size <= sizeof(buf_t))
		return NULL;
	/* Calculate buffer length */
	b = (buf_t *) pool;
	b->size = size - sizeof(buf_t);
	/* Assign pointers */
	b->write_ptr = b->read_ptr = b->data;
	b->filled = 0;
	return b;
}

/**
 * Puts some data to the buffer. Returns the number of bytes actually written.
 */
static inline int buf_put(buf_t *buf, void *data, size_t size)
{
	int count;
	int ret = 0;

	if (buf->filled == buf->size)
		return 0;

	if (buf->write_ptr >= buf->read_ptr) {
		/* 
		 *  (--head--[R]....[W]--tail--)
		 */
		int tailsize = (buf->data + buf->size) - buf->write_ptr;

		count = MIN(tailsize, size);
		memcpy(buf->write_ptr, data, count);
		data += count; /* Move data pointer */
		ret += count;	/* Accumulate number of written bytes */

		buf->write_ptr += count;
		if (buf->write_ptr == buf->data + buf->size)
			buf->write_ptr = buf->data;

		/* Copy rest of data if available to the head */
		if (ret < size) {
			int headsize = (buf->read_ptr - buf->data);
			count = MIN(headsize, size - ret);
			memcpy(buf->write_ptr, data, count);
			ret += count;
			buf->write_ptr += count;
		}
	} else {
		/*
		 *  (....[W]--buffer--[R]....)
		 */
		int bufsize = (buf->read_ptr - buf->write_ptr);
		count = MIN(bufsize, size);
		memcpy(buf->write_ptr, data, count);
		ret += count;
		buf->write_ptr += count;
	}

	buf->filled += ret;
	return ret;
}

/**
 * Put a single byte to the buffer
 */
static inline int buf_putc(buf_t *buf, char c)
{
	if (buf->filled == buf->size)
		return 0;

	*buf->write_ptr = c;

	buf->write_ptr++;
	buf->filled++;

	if (buf->write_ptr == buf->data + buf->size)
		buf->write_ptr = buf->data;
	return 1;
}

/**
 * Reads some data from the buffer. Returns the number of bytes actually read
 */
static inline int buf_get(buf_t *buf, void *data, size_t size)
{
	int count;
	int ret = 0;

	if (buf->filled == 0)
		return 0;

	if (buf->write_ptr > buf->read_ptr) {
		int bufsize = (buf->write_ptr - buf->read_ptr);
		count = MIN(bufsize, size);
		memcpy(data, buf->read_ptr, count);
		ret += count;
		buf->read_ptr += count;
	} else {
		int tailsize = (buf->data + buf->size) - buf->read_ptr;

		count = MIN(tailsize, size);
		memcpy(data, buf->read_ptr, count);
		data += count;
		ret += count;

		buf->read_ptr += count;
		if (buf->read_ptr == buf->data + buf->size)
			buf->read_ptr = buf->data;

		if (ret < size) {
			int headsize = buf->write_ptr - buf->data;
			count = MIN(headsize, size - ret);
			memcpy(data, buf->read_ptr, count);
			ret += count;
			buf->read_ptr += count;
		}
	}
	buf->filled -= ret;
	return ret;
}

/**
 * Get a single byte from the buffer
 */
static inline int buf_getc(buf_t *buf, char *c)
{
	if (buf->filled == 0)
		return 0;
	
	*c = *buf->read_ptr++;
	buf->filled--;

	if (buf->read_ptr == buf->data + buf->size)
		buf->read_ptr = buf->data;

	return 1;
}


/**
 * Returns the size of data stored now in the buffer
 */
static inline size_t buf_size(buf_t *buf)
{
	return buf->filled;
}

#endif /* __AVOS_BUFFER_H_ */
